/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/*
 * ErrorConsole.java
 *
 * Created on Jun 12, 2010, 7:33:00 PM
 */

package MainProgram.Console;

import Global.ProgramSettings;
import MainProgram.ProgramLaunchers.ProgramLauncher;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import javax.swing.JOptionPane;
import org.jdesktop.application.Action;
import org.jdesktop.application.ResourceMap;

/**
 *
 * @author Stephen
 */
public class ErrorConsole extends javax.swing.JFrame
{
    /** Creates new form ErrorConsole */
    public ErrorConsole()
    {
        initComponents();
        setIconImage(ProgramLauncher.getProgramIcon(this));
        RefreshContents();
    }
    private static ErrorConsole s_console = null;
    private StringBuilder m_logBuilder = new StringBuilder("Error Log:\r\n*******************\r\n\r\n");
    public static ErrorConsole getConsole()
    {
        if (s_console == null)
        {
            s_console = new ErrorConsole();
        }
        return s_console;
    }
    private Class m_silencedException = null;
    public void SetSilenced(Class exception)
    {
        m_silencedException = exception;
    }
    public Class GetSilenced()
    {
        return m_silencedException;
    }
    
    /**
	 * Displays standard error to the console
	 */
	public void setupErrorStreamListening()
	{
		SynchedByteArrayOutputStream out = new SynchedByteArrayOutputStream(System.err);
		ThreadReader reader = new ThreadReader(out);
		Thread thread = new Thread(reader, "Console standard error reader");
		thread.setDaemon(true);
		thread.start();
		
		PrintStream print = new PrintStream(out);
		System.setErr(print);
	}
	/**
	 * Displays standard output to the console
	 */
	public void setupOutputStreamListening()
	{
		SynchedByteArrayOutputStream out = new SynchedByteArrayOutputStream(System.out);
		ThreadReader reader = new ThreadReader(out);
		Thread thread = new Thread(reader, "Console standard out reader");
		thread.setDaemon(true);
		thread.start();
		
		PrintStream print = new PrintStream(out);
		System.setOut(print);
	}
    
    /**
     * Method not really needed anymore, just write the error to the output and it will automatically pop up in the error console 
     */
    public void appendError(Exception error)
    {        
        if(m_silencedException != null && m_silencedException.isAssignableFrom(error.getClass()))
            return;
        error.printStackTrace(); //Error info will automatically be routed through to the append method
    }
    public void append(String s)
    {
        //m_logBuilder.insert(33, s);
                
        m_logBuilder.append(s);
        
        if(ProgramSettings.LoadSettings().ShowErrorConsoleWhenErrorOccurs)
            this.setVisible(true);
        RefreshContents();
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        v_scrollPane = new javax.swing.JScrollPane();
        v_logTextArea = new javax.swing.JTextArea();
        v_toolbar = new javax.swing.JPanel();
        v_displayErrorHelpButton = new javax.swing.JButton();
        v_copyToClipboardButton = new javax.swing.JButton();
        v_refreshContentsButton = new javax.swing.JButton();
        v_changeFont = new javax.swing.JButton();
        v_labelHolderPanel = new javax.swing.JPanel();
        v_displayTypeCB = new javax.swing.JComboBox();

        org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance().getContext().getResourceMap(ErrorConsole.class);
        setTitle(resourceMap.getString("Form.title")); // NOI18N
        setMinimumSize(new java.awt.Dimension(585, 370));
        setName("Form"); // NOI18N
        addComponentListener(new java.awt.event.ComponentAdapter() {
            public void componentShown(java.awt.event.ComponentEvent evt) {
                formComponentShown(evt);
            }
        });

        v_scrollPane.setBackground(resourceMap.getColor("v_scrollPane.background")); // NOI18N
        v_scrollPane.setBorder(javax.swing.BorderFactory.createTitledBorder(""));
        v_scrollPane.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        v_scrollPane.setAutoscrolls(true);
        v_scrollPane.setName("v_scrollPane"); // NOI18N

        v_logTextArea.setColumns(20);
        v_logTextArea.setFont(resourceMap.getFont("v_logTextArea.font")); // NOI18N
        v_logTextArea.setLineWrap(true);
        v_logTextArea.setRows(5);
        v_logTextArea.setWrapStyleWord(true);
        v_logTextArea.setName("v_logTextArea"); // NOI18N
        v_scrollPane.setViewportView(v_logTextArea);

        v_toolbar.setName("v_toolbar"); // NOI18N

        javax.swing.ActionMap actionMap = org.jdesktop.application.Application.getInstance().getContext().getActionMap(ErrorConsole.class, this);
        v_displayErrorHelpButton.setAction(actionMap.get("DisplayErrorHelp")); // NOI18N
        v_displayErrorHelpButton.setText(resourceMap.getString("v_displayErrorHelpButton.text")); // NOI18N
        v_displayErrorHelpButton.setName("v_displayErrorHelpButton"); // NOI18N

        v_copyToClipboardButton.setAction(actionMap.get("CopyToClipboard")); // NOI18N
        v_copyToClipboardButton.setText(resourceMap.getString("v_copyToClipboardButton.text")); // NOI18N
        v_copyToClipboardButton.setName("v_copyToClipboardButton"); // NOI18N

        v_refreshContentsButton.setAction(actionMap.get("RefreshContents")); // NOI18N
        v_refreshContentsButton.setText(resourceMap.getString("v_refreshContentsButton.text")); // NOI18N
        v_refreshContentsButton.setName("v_refreshContentsButton"); // NOI18N

        v_changeFont.setAction(actionMap.get("ChangeFont")); // NOI18N
        v_changeFont.setText(resourceMap.getString("v_changeFont.text")); // NOI18N
        v_changeFont.setName("v_changeFont"); // NOI18N

        javax.swing.GroupLayout v_toolbarLayout = new javax.swing.GroupLayout(v_toolbar);
        v_toolbar.setLayout(v_toolbarLayout);
        v_toolbarLayout.setHorizontalGroup(
            v_toolbarLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(v_toolbarLayout.createSequentialGroup()
                .addContainerGap()
                .addComponent(v_refreshContentsButton, javax.swing.GroupLayout.PREFERRED_SIZE, 130, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addComponent(v_copyToClipboardButton, javax.swing.GroupLayout.PREFERRED_SIZE, 130, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addComponent(v_displayErrorHelpButton, javax.swing.GroupLayout.PREFERRED_SIZE, 130, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addComponent(v_changeFont, javax.swing.GroupLayout.PREFERRED_SIZE, 130, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap(262, Short.MAX_VALUE))
        );
        v_toolbarLayout.setVerticalGroup(
            v_toolbarLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(v_toolbarLayout.createSequentialGroup()
                .addGroup(v_toolbarLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(v_toolbarLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
                        .addComponent(v_refreshContentsButton, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 40, Short.MAX_VALUE)
                        .addComponent(v_copyToClipboardButton, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, 40, Short.MAX_VALUE))
                    .addComponent(v_displayErrorHelpButton, javax.swing.GroupLayout.DEFAULT_SIZE, 40, Short.MAX_VALUE)
                    .addComponent(v_changeFont, javax.swing.GroupLayout.DEFAULT_SIZE, 40, Short.MAX_VALUE))
                .addContainerGap())
        );

        v_labelHolderPanel.setName("v_labelHolderPanel"); // NOI18N

        v_displayTypeCB.setFont(resourceMap.getFont("v_displayTypeCB.font")); // NOI18N
        v_displayTypeCB.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Error Log", "Memory Usage", "Program Settings", "Other Information", "System Properties", "Complete Debug Dump" }));
        v_displayTypeCB.setName("v_displayTypeCB"); // NOI18N
        v_displayTypeCB.setPreferredSize(new java.awt.Dimension(250, 35));
        v_displayTypeCB.addItemListener(new java.awt.event.ItemListener() {
            public void itemStateChanged(java.awt.event.ItemEvent evt) {
                v_displayTypeCBItemStateChanged(evt);
            }
        });
        v_labelHolderPanel.add(v_displayTypeCB);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(v_scrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 822, Short.MAX_VALUE)
            .addComponent(v_labelHolderPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 822, Short.MAX_VALUE)
            .addComponent(v_toolbar, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(v_labelHolderPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 46, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addComponent(v_scrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 381, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(v_toolbar, javax.swing.GroupLayout.PREFERRED_SIZE, 50, javax.swing.GroupLayout.PREFERRED_SIZE))
        );

        java.awt.Dimension screenSize = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
        setBounds((screenSize.width-838)/2, (screenSize.height-543)/2, 838, 543);
    }// </editor-fold>//GEN-END:initComponents

    private void v_displayTypeCBItemStateChanged(java.awt.event.ItemEvent evt)//GEN-FIRST:event_v_displayTypeCBItemStateChanged
    {//GEN-HEADEREND:event_v_displayTypeCBItemStateChanged
        RefreshContents();
    }//GEN-LAST:event_v_displayTypeCBItemStateChanged

    private void formComponentShown(java.awt.event.ComponentEvent evt)//GEN-FIRST:event_formComponentShown
    {//GEN-HEADEREND:event_formComponentShown
        RefreshContents();
}//GEN-LAST:event_formComponentShown
     public static String getMemory()
     {
         System.gc();
         StringBuilder buf = new StringBuilder("Memory Usage:\n");
            buf.append("*******************\r\n\r\n");
            buf.append("Total memory: " + Runtime.getRuntime().totalMemory()).append(" bytes");
            buf.append("\r\n");
            buf.append("Free memory: " + Runtime.getRuntime().freeMemory()).append(" bytes");
            buf.append("\r\n");
         buf.append("Max memory: " + Runtime.getRuntime().maxMemory()).append(" bytes");
         buf.append("\r\n");
         return buf.toString();
     }
    @SuppressWarnings("unchecked")
    public static String getSystemProperties()
    {
        StringBuilder buf = new StringBuilder("System Properties\n*******************\r\n\r\n");
        Properties props = System.getProperties();
        java.util.List keys = new ArrayList(props.keySet());

        Collections.sort(keys);

        Iterator iter = keys.iterator();
        while (iter.hasNext())
        {
            String property = (String) iter.next();
            String value = props.getProperty(property);
            buf.append(property).append(": ").append(value.replace("\r\n", "/r/n").replace("\n", "/n")).append("\n");
        }

        return buf.toString();
    }
    public static String getProgramSettings()
    {
        StringBuilder buf = new StringBuilder("Program Settings\n*******************\r\n\r\n");
        ProgramSettings settings = ProgramSettings.LoadSettings();
        
        //General
        buf.append("ShowErrorConsoleWhenErrorOccurs").append(": ").append(settings.ShowErrorConsoleWhenErrorOccurs).append("\n");
        
        //Graphics
        buf.append("ProgramLookAndFeelClassName").append(": ").append(settings.ProgramLookAndFeelClassName).append("\n");
        buf.append("ProgramLookAndFeelIndex").append(": ").append(settings.ProgramLookAndFeelIndex).append("\n");
        buf.append("GraphicsBufferingIndex").append(": ").append(settings.GraphicsBufferingIndex).append("\n");
        
        //Advanced
        buf.append("MaxJavaHeapSize").append(": ").append(settings.MaxJavaHeapSize).append("\n");

        return buf.toString();
    }

    public static String getOtherInfo()
    {
        StringBuilder buf = new StringBuilder("Other Information\n*******************\r\n\r\n");
        return buf.toString();
    }

    @Action
    public void RefreshContents()
    {
        if (v_displayTypeCB.getSelectedIndex() == 0)
        {
            v_logTextArea.setText(m_logBuilder.toString());
        }
        else if (v_displayTypeCB.getSelectedIndex() == 1)
        {
            v_logTextArea.setText(getMemory());
        }
        else if (v_displayTypeCB.getSelectedIndex() == 2)
        {
            v_logTextArea.setText(getProgramSettings());
        }
        else if (v_displayTypeCB.getSelectedIndex() == 3)
        {
            v_logTextArea.setText(getOtherInfo());
        }
        else if (v_displayTypeCB.getSelectedIndex() == 4)
        {
            v_logTextArea.setText(getSystemProperties());
        }
        else
        {
            v_logTextArea.setText(m_logBuilder.toString() + "\r\n\r\n\r\n" + getMemory() + "\r\n\r\n\r\n" + getProgramSettings() + "\r\n\r\n\r\n" + getOtherInfo() + "\r\n\r\n\r\n" + getSystemProperties());
        }
    }

    @Action
    public void CopyToClipboard()
    {
        String text = v_logTextArea.getText();
        StringSelection select = new StringSelection(text);
        Toolkit.getDefaultToolkit().getSystemClipboard().setContents(select, select);
    }

    @Action
    public void DisplayErrorHelp()
    {        
    }

    @Action
    public void ChangeFont()
    {
        List<String> fonts = Arrays.asList("Monospaced", "Arial");
        
        int result = JOptionPane.showOptionDialog(this, "Select the new error console font: ", "Select Console Font", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, fonts.toArray(), fonts.toArray()[1]);

        if (result < 0)
            return;

        String selectedFont = (String) fonts.toArray()[result];
        
        if(selectedFont.equals("Monospaced"))
            v_logTextArea.setFont(new Font("Monospaced", Font.PLAIN, 12));
        else if(selectedFont.equals("Arial"))
            v_logTextArea.setFont(new Font("Arial", Font.PLAIN, 12));
    }
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton v_changeFont;
    private javax.swing.JButton v_copyToClipboardButton;
    private javax.swing.JButton v_displayErrorHelpButton;
    private javax.swing.JComboBox v_displayTypeCB;
    private javax.swing.JPanel v_labelHolderPanel;
    private javax.swing.JTextArea v_logTextArea;
    private javax.swing.JButton v_refreshContentsButton;
    private javax.swing.JScrollPane v_scrollPane;
    private javax.swing.JPanel v_toolbar;
    // End of variables declaration//GEN-END:variables
}



class ThreadReader implements Runnable
{
	private SynchedByteArrayOutputStream m_in;
	
	ThreadReader(SynchedByteArrayOutputStream in)
	{
		m_in = in;
	}
	
	public void run()
	{
		while(true)
		{
            ErrorConsole.getConsole().append(m_in.readFully());
		}
	}
}

/**
 * Allows data written to a byte output stream to be read
 * safely friom a seperate thread.
 *
 * Only readFully() is currently threadSafe for reading.
 * 
 */
class SynchedByteArrayOutputStream extends ByteArrayOutputStream
{
	private Object lock = new Object();
	private PrintStream m_mirror;
	
	SynchedByteArrayOutputStream(PrintStream mirror)
	{
	    m_mirror = mirror;
	}
	
	public void write(byte b) throws IOException
	{
		synchronized(lock)
		{
		    m_mirror.write(b);
			super.write(b);
			lock.notifyAll();
		}
	}
	
	public void write(byte[] b, int off, int len) 
	{
		synchronized(lock)
		{
			super.write(b, off, len);
			m_mirror.write(b,off,len);
			lock.notifyAll();
		}
	}

	
	/** 
	 * Read all data written to the stream. 
	 * Blocks until data is available.
	 * This is currently the only threadsafe method for reading.
	 */
	public String readFully()
	{
		synchronized(lock)
		{
			if(super.size() == 0)
			{
				try
				{
					lock.wait();
				} catch(InterruptedException ie)
				{}
			}
			String s = toString();
			reset();
			return s;
			
		}
	}
}
